#include "helper.h"
#include "monitor.h"
#include "reg.h"
#include "special.h"
#include "golden_trace.h"



extern uint32_t instr;
extern char assembly[80];

/* decode R-type instrucion */
static void decode_r_type(uint32_t instr) {

	op_src1->type = OP_TYPE_REG;
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	op_src1->val = reg_w(op_src1->reg);
	
	op_src2->type = OP_TYPE_REG;
	op_src2->imm = (instr & RT_MASK) >> (RD_SIZE + SHAMT_SIZE + FUNC_SIZE);
	op_src2->val = reg_w(op_src2->reg);

	op_dest->type = OP_TYPE_REG;
	op_dest->reg = (instr & RD_MASK) >> (SHAMT_SIZE + FUNC_SIZE);
}

make_helper(add) {

	decode_r_type(instr);
	int32_t tmp = (int32_t)op_src1->val + (int32_t)op_src2->val;
	if(((int32_t)(op_src1->val) > 0 && (int32_t)(op_src2->val) > 0 && tmp < 0)
		|| ((int32_t)(op_src1->val) < 0 && (int32_t)(op_src2->val) < 0 && tmp > 0)){
		// Overflow
		CP0_CHANGE(Cause, ExcCode, 0x0c);
		SignalException(tmp);
	}
	else reg_w(op_dest->reg) = tmp;
	sprintf(assembly, "add   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(addu) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (uint32_t)op_src1->val + (uint32_t)op_src2->val;
	sprintf(assembly, "addu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sub) {

	decode_r_type(instr);
	int32_t tmp = (int32_t)op_src1->val - (int32_t)op_src2->val;
	if(((int32_t)(op_src1->val) > 0 && (int32_t)(op_src2->val) < 0 && tmp < 0)
		|| ((int32_t)(op_src1->val) < 0 && (int32_t)(op_src2->val) > 0 && tmp > 0)){
		// Overflow
		CP0_CHANGE(Cause, ExcCode, 0x0c);
		SignalException(tmp);
	}
	else reg_w(op_dest->reg) = tmp;
	sprintf(assembly, "sub   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(subu) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((uint32_t)(op_src1->val) - (uint32_t)(op_src2->val));
	sprintf(assembly, "subu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));

}

make_helper(slt) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((int32_t)(op_src1->val) < (int32_t)(op_src2->val)) ? 1 : 0;
	sprintf(assembly, "slt   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sltu) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((uint32_t)(op_src1->val) < (uint32_t)(op_src2->val)) ? 1 : 0;
	sprintf(assembly, "sltu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(div) {

	decode_r_type(instr);
	cpu.lo = ((int32_t)op_src1->val / (int32_t)op_src2->val);
	cpu.hi = ((int32_t)op_src1->val % (int32_t)op_src2->val);
	sprintf(assembly, "div   %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(divu) {

	decode_r_type(instr);
	cpu.lo = ((uint32_t)(op_src1->val) / (uint32_t)(op_src2->val));
	cpu.hi = ((uint32_t)(op_src1->val) % (uint32_t)(op_src2->val));
	sprintf(assembly, "divu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(mult) {

	decode_r_type(instr);
	int64_t prod = ((int64_t)(int32_t)op_src1->val * (int64_t)(int32_t)op_src2->val);
	cpu.lo = (prod);
	cpu.hi = (prod >> 32);
	sprintf(assembly, "mult   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(multu) {

	decode_r_type(instr);
	long prod = ((uint64_t)(op_src1->val) * (uint64_t)(op_src2->val));
	cpu.lo = (prod);
	cpu.hi = (prod >> 32);
	sprintf(assembly, "multu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(and) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((uint32_t)op_src1->val & (uint32_t)op_src2->val);
	sprintf(assembly, "and   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(nor) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ~((uint32_t)op_src1->val | (uint32_t)op_src2->val);
	sprintf(assembly, "nor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(or) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((uint32_t)op_src1->val | (uint32_t)op_src2->val);
	sprintf(assembly, "or   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(xor) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((uint32_t)op_src1->val ^ (uint32_t)op_src2->val);
	sprintf(assembly, "xor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sllv) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((uint32_t)op_src2->val << op_src1->val);
	sprintf(assembly, "sllv   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sll) {

	decode_r_type(instr);
	uint32_t sa = (instr & SHAMT_MASK) >> (FUNC_SIZE);
	reg_w(op_dest->reg) = ((uint32_t)op_src2->val << sa);
	sprintf(assembly, "sll   %s,   %s,   sa", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(srav) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((int32_t)op_src2->val >> op_src1->val);
	sprintf(assembly, "srav   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sra) {

	decode_r_type(instr);
	uint32_t sa = (instr & SHAMT_MASK) >> (FUNC_SIZE);
	reg_w(op_dest->reg) = ((int32_t)op_src2->val >> sa);
	sprintf(assembly, "sra   %s,   %s,   sa", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(srlv) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((uint32_t)(op_src2->val) >> op_src1->val);
	sprintf(assembly, "srlv   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(srl) {

	decode_r_type(instr);
	uint32_t sa = (instr & SHAMT_MASK) >> (FUNC_SIZE);
	reg_w(op_dest->reg) = ((uint32_t)(op_src2->val) >> sa);
	sprintf(assembly, "srl   %s,   %s,   sa", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(beq) {

	decode_r_type(instr);
	int32_t offset = ((int32_t)(int16_t)(instr & IMM_MASK) << 2);
	if(op_src1->val == op_src2->val) {
		GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, cpu.pc + offset);
		cpu.pc += offset;
	}
	sprintf(assembly, "beq   %s,   %s,   0x%x", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg), offset);
}

make_helper(bne) {

	decode_r_type(instr);
	int32_t offset = ((int32_t)(int16_t)(instr & IMM_MASK) << 2);
	if(op_src1->val != op_src2->val) {
		GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, cpu.pc + offset);
		cpu.pc += offset;
	}
	sprintf(assembly, "bne   %s,   %s,   0x%x", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg), offset);
}

make_helper(bgez) {

	decode_r_type(instr);
	int32_t offset = ((int32_t)(int16_t)(instr & IMM_MASK) << 2);
	if((int32_t)op_src1->val >= 0) {
		GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, cpu.pc + offset);
		cpu.pc += offset;
	}
	sprintf(assembly, "bgez   %s,   0x%x", REG_NAME(op_src1->reg), offset);
}

make_helper(bgtz) {

	decode_r_type(instr);
	int32_t offset = ((int32_t)(int16_t)(instr & IMM_MASK) << 2);
	if((int32_t)op_src1->val > 0) {
		GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, cpu.pc + offset);
		cpu.pc += offset;
	}
	sprintf(assembly, "bgtz   %s,   0x%x", REG_NAME(op_src1->reg), offset);
}

make_helper(blez) {

	decode_r_type(instr);
	int32_t offset = ((int32_t)(int16_t)(instr & IMM_MASK) << 2);
	if((int32_t)op_src1->val <= 0) {
		GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, cpu.pc + offset);
		cpu.pc += offset;
	}
	sprintf(assembly, "blez   %s,   0x%x", REG_NAME(op_src1->reg), offset);
}

make_helper(bltz) {

	decode_r_type(instr);
	int32_t offset = ((int32_t)(int16_t)(instr & IMM_MASK) << 2);
	if((int32_t)op_src1->val < 0) {
		GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, cpu.pc + offset);
		cpu.pc += offset;
	}
	sprintf(assembly, "bltz   %s,   0x%x", REG_NAME(op_src1->reg), offset);
}

make_helper(bgezal) {

	decode_r_type(instr);
	int32_t offset = ((int32_t)(int16_t)(instr & IMM_MASK) << 2);
	cpu.ra = cpu.pc + 8;
	if((int32_t)op_src1->val >= 0) {
		GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, cpu.pc + offset);
		cpu.pc += offset;
	}
	sprintf(assembly, "bgezal   %s,   0x%x", REG_NAME(op_src1->reg), offset);
}

make_helper(bltzal) {

	decode_r_type(instr);
	int32_t offset = ((int32_t)(int16_t)(instr & IMM_MASK) << 2);
	cpu.ra = cpu.pc + 8;
	if((int32_t)op_src1->val < 0) {
		GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, cpu.pc + offset);
		cpu.pc += offset;
	}
	sprintf(assembly, "bltzal   %s,   0x%x", REG_NAME(op_src1->reg), offset);
}

make_helper(bz) {
	
	decode_r_type(instr);
	switch(op_src2->imm){
		case 0x00: bltz(pc); break;
		case 0x01: bgez(pc); break;
		case 0x11: bgezal(pc); break;
		case 0x10: bltzal(pc); break;
		default: GUI_printf(RED_TEXT, "'inv' at $pc: 0x%08x", cpu.pc);
	}
}

make_helper(jr) {

	decode_r_type(instr);
	GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, op_src1->val);
	cpu.pc = op_src1->val - 4;
	sprintf(assembly, "jr   %s", REG_NAME(op_src1->reg));
}

make_helper(jalr) {

	decode_r_type(instr);
	GUI_printf(GREEN_TEXT, "pc: 0x%08x -> 0x%08x", cpu.pc, op_src1->val);
	reg_w(op_dest->reg) = cpu.pc + 8;
	cpu.pc = op_src1->val - 4;
	sprintf(assembly, "bltzal   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(mfhi) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.hi;
	sprintf(assembly, "mfhi   %s", REG_NAME(op_dest->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(mflo) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.lo;
	sprintf(assembly, "mflo   %s", REG_NAME(op_dest->reg));
	golden_trace(cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(mthi) {

	decode_r_type(instr);
	cpu.hi = op_src1->val;
	sprintf(assembly, "mthi   %s", REG_NAME(op_src1->reg));
}

make_helper(mtlo) {

	decode_r_type(instr);
	cpu.lo = op_src1->val;
	sprintf(assembly, "mtlo   %s", REG_NAME(op_src1->reg));
}

make_helper(_break) {

	decode_r_type(instr);
	CP0_CHANGE(Cause, ExcCode, 0x09);
	SignalException(0);
	sprintf(assembly, "break");
}

make_helper(syscall) {

	decode_r_type(instr);
	CP0_CHANGE(Cause, ExcCode, 0x08);
	SignalException(0);
	sprintf(assembly, "syscall");
}

make_helper(eret) {

	decode_r_type(instr);
	cpu.pc = cp0.EPC;
	CP0_CHANGE(Status, EXL, 0);
	memset(cpu.gpr, 0, sizeof(cpu.gpr));
	sprintf(assembly, "eret");
}

make_helper(mfc0) {

	decode_r_type(instr);
	reg_w(op_src2->imm) = cp0.cp0[op_dest->reg % 32]._32;
	sprintf(assembly, "mfc0   %s   %s", regfile[op_src2->imm], cp0regfile[op_dest->reg]);
}

make_helper(mtc0) {

	decode_r_type(instr);
	cp0.cp0[op_dest->reg % 32]._32 = reg_w(op_src2->imm);
	sprintf(assembly, "mtc0   %s   %s", cp0regfile[op_src2->imm], regfile[op_dest->reg]);
}

make_helper(_ERET) {

	decode_r_type(instr);
	if((instr & 0x3F) == 0x18){
		eret(cpu.pc);
	}
	else{
		if(((instr & 0x3E00000) >> 20) == 0x0){
			mfc0(cpu.pc);
		}
		else if(((instr & 0x3E00000) >> 20) == 0x8){
			mtc0(cpu.pc);
		}
	}
}